import { ConnectionPool } from '@/core/connection-pool';
import { Producer } from '@/core/producer';
import { Consumer } from '@/core/consumer';
import { Message } from '@/core/message';
import { MessageHandler, ExchangeConfig, ExchangeType, Logger } from '@/types';
import { createLogger } from '@/utils/logger';
import { generateUUID } from '@/utils/uuid';

/**
 * Router pattern implementation
 * Routes messages based on routing keys and patterns
 */
export class Router {
  protected readonly connectionPool: ConnectionPool;
  protected readonly exchangeName: string;
  protected readonly exchangeConfig: ExchangeConfig;
  protected readonly producer: Producer;
  protected readonly logger: Logger;
  protected readonly routes = new Map<string, Consumer[]>();

  constructor(
    connectionPool: ConnectionPool,
    exchangeName: string,
    exchangeConfig: Partial<ExchangeConfig> = {},
    logger?: Logger
  ) {
    this.connectionPool = connectionPool;
    this.exchangeName = exchangeName;
    this.exchangeConfig = {
      name: exchangeName,
      type: ExchangeType.TOPIC,
      durable: true,
      autoDelete: false,
      ...exchangeConfig,
    };
    this.producer = new Producer(connectionPool);
    this.logger = logger ?? createLogger('Router');
  }

  /**
   * Setup the exchange
   */
  async setupExchange(): Promise<void> {
    try {
      const connection = await this.connectionPool.getConnection();
      const channel = connection.channel;

      await channel.assertExchange(this.exchangeConfig.name, this.exchangeConfig.type, {
        durable: this.exchangeConfig.durable,
        autoDelete: this.exchangeConfig.autoDelete,
        arguments: this.exchangeConfig.arguments,
      });

      this.logger.info(`Router exchange ${this.exchangeName} setup completed`);
    } catch (error) {
      this.logger.error(`Failed to setup router exchange ${this.exchangeName}`, error as Error);
      throw error;
    }
  }

  /**
   * Send a message with a routing key
   */
  async send(routingKey: string, messageData: unknown): Promise<void> {
    const message = new Message(messageData);
    message.addHeader('routingKey', routingKey);

    await this.producer.send(message, {
      exchange: this.exchangeName,
      routingKey,
      persistent: true,
    });

    this.logger.debug(`Message sent to exchange ${this.exchangeName}`, {
      messageId: message.id,
      routingKey,
    });
  }

  /**
   * Send multiple messages with different routing keys
   */
  async sendBatch(messages: Array<{ routingKey: string; data: unknown }>): Promise<void> {
    const messageObjects = messages.map(msg => {
      const message = new Message(msg.data);
      message.addHeader('routingKey', msg.routingKey);
      return message;
    });

    const results = await this.producer.sendBatch(messageObjects, {
      exchange: this.exchangeName,
      persistent: true,
    });

    const successCount = results.filter(r => r).length;
    this.logger.info(
      `Sent ${successCount}/${messages.length} messages to exchange ${this.exchangeName}`
    );
  }

  /**
   * Subscribe to messages matching a routing pattern
   */
  async subscribe(
    routingPattern: string,
    handler: MessageHandler,
    options: {
      queueName?: string;
      exclusive?: boolean;
      autoDelete?: boolean;
      autoAck?: boolean;
      durable?: boolean;
    } = {}
  ): Promise<string> {
    const queueName =
      options.queueName || `${this.exchangeName}_${routingPattern}_${generateUUID()}`;

    try {
      const connection = await this.connectionPool.getConnection();
      const channel = connection.channel;

      // Declare queue
      await channel.assertQueue(queueName, {
        exclusive: options.exclusive ?? false,
        autoDelete: options.autoDelete ?? true,
        durable: options.durable ?? false,
      });

      // Bind queue to exchange with routing pattern
      await channel.bindQueue(queueName, this.exchangeName, routingPattern);

      // Create consumer
      const consumer = new Consumer(this.connectionPool, handler);
      await consumer.startConsuming({
        queueName,
        autoAck: options.autoAck ?? false,
        exclusive: options.exclusive ?? false,
      });

      // Track the consumer
      if (!this.routes.has(routingPattern)) {
        this.routes.set(routingPattern, []);
      }
      this.routes.get(routingPattern)!.push(consumer);

      this.logger.info(`Subscribed to routing pattern ${routingPattern} with queue ${queueName}`);

      return queueName;
    } catch (error) {
      this.logger.error(`Failed to subscribe to routing pattern ${routingPattern}`, error as Error);
      throw error;
    }
  }

  /**
   * Unsubscribe from a routing pattern
   */
  async unsubscribe(routingPattern: string, queueName?: string): Promise<void> {
    const consumers = this.routes.get(routingPattern);
    if (!consumers || consumers.length === 0) {
      this.logger.warn(`No consumers found for routing pattern ${routingPattern}`);
      return;
    }

    try {
      const connection = await this.connectionPool.getConnection();
      const channel = connection.channel;

      if (queueName) {
        // Unsubscribe specific queue
        const consumerIndex = consumers.findIndex(consumer =>
          consumer.getStatus().consumerTag?.includes(queueName)
        );

        if (consumerIndex !== -1) {
          await consumers[consumerIndex].stopConsuming();
          consumers.splice(consumerIndex, 1);

          await channel.unbindQueue(queueName, this.exchangeName, routingPattern);
          await channel.deleteQueue(queueName);

          this.logger.info(
            `Unsubscribed queue ${queueName} from routing pattern ${routingPattern}`
          );
        }
      } else {
        // Unsubscribe all consumers for this pattern
        await Promise.all(consumers.map(consumer => consumer.stopConsuming()));
        consumers.length = 0;

        this.logger.info(`Unsubscribed all consumers from routing pattern ${routingPattern}`);
      }

      // Clean up empty route
      if (consumers.length === 0) {
        this.routes.delete(routingPattern);
      }
    } catch (error) {
      this.logger.error(
        `Failed to unsubscribe from routing pattern ${routingPattern}`,
        error as Error
      );
      throw error;
    }
  }

  /**
   * Get all active routes
   */
  getRoutes(): Record<string, number> {
    const routes: Record<string, number> = {};

    for (const [pattern, consumers] of this.routes) {
      routes[pattern] = consumers.length;
    }

    return routes;
  }

  /**
   * Stop all consumers
   */
  async stopAllConsumers(): Promise<void> {
    const allConsumers: Consumer[] = [];

    for (const consumers of this.routes.values()) {
      allConsumers.push(...consumers);
    }

    await Promise.all(allConsumers.map(consumer => consumer.stopConsuming()));
    this.routes.clear();

    this.logger.info(`Stopped all consumers for router ${this.exchangeName}`);
  }

  /**
   * Delete the exchange
   */
  async deleteExchange(options: { ifUnused?: boolean } = {}): Promise<void> {
    try {
      const connection = await this.connectionPool.getConnection();
      const channel = connection.channel;

      await channel.deleteExchange(this.exchangeName, options);
      this.logger.info(`Deleted exchange ${this.exchangeName}`);
    } catch (error) {
      this.logger.error(`Failed to delete exchange ${this.exchangeName}`, error as Error);
      throw error;
    }
  }

  /**
   * Close the router
   */
  async close(): Promise<void> {
    await this.stopAllConsumers();
    await this.producer.close();
    this.logger.info(`Router ${this.exchangeName} closed`);
  }
}

/**
 * Direct Router for exact routing key matches
 */
export class DirectRouter extends Router {
  constructor(
    connectionPool: ConnectionPool,
    exchangeName: string,
    exchangeConfig: Partial<ExchangeConfig> = {},
    logger?: Logger
  ) {
    const directExchangeConfig = {
      ...exchangeConfig,
      type: ExchangeType.DIRECT,
    };

    super(connectionPool, exchangeName, directExchangeConfig, logger);
  }

  /**
   * Subscribe to exact routing key
   */
  async subscribeToRoute(
    routingKey: string,
    handler: MessageHandler,
    options: {
      queueName?: string;
      exclusive?: boolean;
      autoDelete?: boolean;
      autoAck?: boolean;
      durable?: boolean;
    } = {}
  ): Promise<string> {
    return await this.subscribe(routingKey, handler, options);
  }
}

/**
 * Headers Router for header-based routing
 */
export class HeadersRouter extends Router {
  constructor(
    connectionPool: ConnectionPool,
    exchangeName: string,
    exchangeConfig: Partial<ExchangeConfig> = {},
    logger?: Logger
  ) {
    const headersExchangeConfig = {
      ...exchangeConfig,
      type: ExchangeType.HEADERS,
    };

    super(connectionPool, exchangeName, headersExchangeConfig, logger);
  }

  /**
   * Send message with headers
   */
  async sendWithHeaders(
    headers: Record<string, string | number | boolean>,
    messageData: unknown
  ): Promise<void> {
    const message = new Message(messageData);
    message.addHeaders(headers);

    await this.producer.send(message, {
      exchange: this.exchangeName,
      persistent: true,
      headers,
    });

    this.logger.debug(`Message sent to headers exchange ${this.exchangeName}`, {
      messageId: message.id,
      headers,
    });
  }

  /**
   * Subscribe to messages matching header criteria
   */
  async subscribeToHeaders(
    headerCriteria: Record<string, string | number | boolean>,
    matchType: 'any' | 'all',
    handler: MessageHandler,
    options: {
      queueName?: string;
      exclusive?: boolean;
      autoDelete?: boolean;
      autoAck?: boolean;
      durable?: boolean;
    } = {}
  ): Promise<string> {
    const queueName = options.queueName || `${this.exchangeName}_headers_${generateUUID()}`;

    try {
      const connection = await this.connectionPool.getConnection();
      const channel = connection.channel;

      // Declare queue
      await channel.assertQueue(queueName, {
        exclusive: options.exclusive ?? false,
        autoDelete: options.autoDelete ?? true,
        durable: options.durable ?? false,
      });

      // Bind queue to exchange with header criteria
      const bindingArgs = {
        ...headerCriteria,
        'x-match': matchType,
      };

      await channel.bindQueue(queueName, this.exchangeName, '', bindingArgs);

      // Create consumer
      const consumer = new Consumer(this.connectionPool, handler);
      await consumer.startConsuming({
        queueName,
        autoAck: options.autoAck ?? false,
        exclusive: options.exclusive ?? false,
      });

      // Track the consumer
      const routingKey = `headers_${matchType}_${JSON.stringify(headerCriteria)}`;
      if (!this.routes.has(routingKey)) {
        this.routes.set(routingKey, []);
      }
      this.routes.get(routingKey)!.push(consumer);

      this.logger.info(`Subscribed to headers criteria with queue ${queueName}`, {
        headerCriteria,
        matchType,
      });

      return queueName;
    } catch (error) {
      this.logger.error('Failed to subscribe to headers criteria', error as Error);
      throw error;
    }
  }
}

/**
 * Utility functions for creating routers
 */
export const createRouter = (
  connectionPool: ConnectionPool,
  exchangeName: string,
  exchangeConfig?: Partial<ExchangeConfig>,
  logger?: Logger
): Router => {
  return new Router(connectionPool, exchangeName, exchangeConfig, logger);
};

export const createDirectRouter = (
  connectionPool: ConnectionPool,
  exchangeName: string,
  exchangeConfig?: Partial<ExchangeConfig>,
  logger?: Logger
): DirectRouter => {
  return new DirectRouter(connectionPool, exchangeName, exchangeConfig, logger);
};

export const createHeadersRouter = (
  connectionPool: ConnectionPool,
  exchangeName: string,
  exchangeConfig?: Partial<ExchangeConfig>,
  logger?: Logger
): HeadersRouter => {
  return new HeadersRouter(connectionPool, exchangeName, exchangeConfig, logger);
};
